﻿using System;
using System.Collections.Generic;
using System.Windows.Threading;

using Microsoft.Practices.Composite.Events;

using Matchingo.Common.Types.EventArguments;
using Matchingo.Common.Types.Events;
using Matchingo.Common.Types.Extensions;
using Matchingo.Common.Types.Interfaces;
using Matchingo.Common.Types.Types;

namespace Matchingo.Models.VirtualPlayers
{
    public class AIPlayer : IArtificialPlayer
    {
        private static readonly Random RandomGenerator = new Random();

        #region ========== Private Properties ==========

        private readonly IEventAggregator EventAggregator;

        private IMemoryGameLogic GameLogic { get; set; }
        private TimeSpan DelayBetweenTurns { get; set; }
        private DispatcherTimer AIPlayerTimer { get; set; }
        private Dictionary<Guid, Guid> PlayerMemory { get; set; }
        private Guid QueuedMove { get; set; }

        #endregion

        public AIPlayer(IEventAggregator eventAggregator, IMemoryGameLogic gameLogic)
        {
            EventAggregator = eventAggregator;
            GameLogic = gameLogic;
        }

        #region ========== Public Methods ==========

        public void PlayGame(int delayBetweenTurns)
        {
            PlayerMemory = new Dictionary<Guid, Guid>();
            QueuedMove = Guid.Empty;
            
            EventAggregator.GetEvent<GameEndedEvent>().Subscribe(GameLogicGameEnded);

            AIPlayerTimer = new DispatcherTimer();
            AIPlayerTimer.Interval = new TimeSpan(0, 0, 0, 0, delayBetweenTurns);
            AIPlayerTimer.Tick += (s, e) => TakeNextTurn();
            AIPlayerTimer.Start();
        }

        public void StopPlayingGame()
        {
            if (AIPlayerTimer == null) 
                return;

            EventAggregator.GetEvent<GameEndedEvent>().Unsubscribe(GameLogicGameEnded);
            AIPlayerTimer.Stop();
        }

        #endregion

        #region ========== Event Handlers ==========

        // NOTE:: method must be public to work with EventAggregator in Silverlight
        public void GameLogicGameEnded(GameEndedEventArgs args)
        {
            StopPlayingGame();
        }

        #endregion

        #region ========== Private Methods ==========

        private void TakeNextTurn()
        {
            if (GameLogic.CurrentGame == null || 
                GameLogic.CurrentGame.GameState != Game.GameStateEnum.Started)
            {
                StopPlayingGame();
                return;
            }
            
            bool flipped = false;

            while (!flipped)
            {
                // see if we have memory of a card with this matching id, but a different uniqueid,
                // if so then use that card to match with, otherwise just pick a random card
                if (GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched().Count == 1)
                {
                    Guid faceUpMatchingID = GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[0].MatchingID;
                    if (PlayerMemory.ContainsKey(faceUpMatchingID))
                    {
                        Guid faceUpUniqueID = GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[0].UniqueID;
                        Guid memoryUniqueID = PlayerMemory[GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[0].MatchingID];
                        if (faceUpUniqueID != memoryUniqueID)
                        {
                            GameLogic.Flip(memoryUniqueID);
                            flipped = true;
                            continue;
                        }
                    }
                } // count can never be 0 except on the first turn, once two are up then a flip leaves 1 still showing
                else if (GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched().Count == 2)
                {
                    if (QueuedMove != Guid.Empty)
                    {
                        Guid nonMatchingUniqueID = GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[0].UniqueID == QueuedMove ? 
                            GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[1].UniqueID : 
                                                                                                                                                                                      GameLogic.CurrentGame.CardsSet.GetCardsFaceUpNotMatched()[0].UniqueID;

                        GameLogic.Flip(nonMatchingUniqueID);
                        flipped = true;
                        QueuedMove = Guid.Empty;
                        continue;
                    }
                }

                int randomCard = RandomGenerator.Next(GameLogic.CurrentGame.CardsSet.GetCardsFaceDown().Count);
                Card c = GameLogic.CurrentGame.CardsSet.GetCardsFaceDown()[randomCard];

                if (c.VisibleSide != Card.CardSide.Front && !c.IsMatched)
                {
                    GameLogic.Flip(c.UniqueID);
                    flipped = true;

                    // see if our memory already has this matchingID, if so then set this matching UniqueID
                    // to our next suggested move
                    if (PlayerMemory.ContainsKey(c.MatchingID))
                    {
                        if (PlayerMemory[c.MatchingID] != c.UniqueID)
                        {
                            QueuedMove = c.UniqueID;
                        }
                    }
                    else
                    {
                        PlayerMemory.Add(c.MatchingID, c.UniqueID);
                    }
                }
            }
        }

        #endregion
    }
}
